/*
* Copyright 2016, Stuart Douglas, and individual contributors as indicated
* by the @authors tag.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fakereplace.core;
import java.beans.Introspector;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;
import org.fakereplace.api.Extension;
import org.fakereplace.api.NewClassData;
import org.fakereplace.classloading.ClassLookupManager;
import org.fakereplace.data.BaseClassData;
import org.fakereplace.data.ClassDataBuilder;
import org.fakereplace.data.ClassDataStore;
import org.fakereplace.replacement.AddedClass;
import org.fakereplace.replacement.AnnotationTransformer;
import org.fakereplace.replacement.FieldReplacementTransformer;
import org.fakereplace.replacement.MethodReplacementTransformer;
import org.fakereplace.server.FakereplaceServer;
import org.fakereplace.transformation.ClassLoaderTransformer;
import org.fakereplace.transformation.MainTransformer;
import org.fakereplace.transformation.UnmodifiedFileIndex;
import javassist.bytecode.ClassFile;
/**
* The agent entry point.
*
* @author stuart
*/
public class Agent {
private static final Class[] EMPTY_CL_ARRAY = new Class[0];
private static volatile Instrumentation inst;
private static volatile MainTransformer mainTransformer;
public static void premain(java.lang.String s, java.lang.instrument.Instrumentation i) {
AgentOptions.setup(s);
inst = i;
final Set<Extension> extension = getIntegrationInfo(ClassLoader.getSystemClassLoader());
//initialise the unmodified file index
UnmodifiedFileIndex.loadIndex();
//first we need to instrument the class loaders
final Set<Class> cls = new HashSet<Class>();
for (Class c : inst.getAllLoadedClasses()) {
if (ClassLoader.class.isAssignableFrom(c)) {
cls.add(c);
}
}
final ClassLoaderTransformer classLoaderTransformer = new ClassLoaderTransformer();
final MainTransformer mainTransformer = new MainTransformer(extension);
Agent.mainTransformer = mainTransformer;
inst.addTransformer(mainTransformer, true);
mainTransformer.addTransformer(classLoaderTransformer);
try {
inst.retransformClasses(cls.toArray(EMPTY_CL_ARRAY));
} catch (UnmodifiableClassException e) {
e.printStackTrace();
}
mainTransformer.addTransformer(new AnnotationTransformer());
mainTransformer.addTransformer(new FieldReplacementTransformer());
mainTransformer.addTransformer(new MethodReplacementTransformer());
mainTransformer.addTransformer(new Transformer(extension));
mainTransformer.setRetransformationStarted(false);
mainTransformer.setLogClassRetransformation(true);
//start the server
String portString = AgentOptions.getOption(AgentOption.SERVER);
if(portString == null || !portString.equals("-1")) {
if(portString == null) {
portString = "6555";
}
Thread thread = new Thread(new FakereplaceServer(Integer.parseInt(portString)));
thread.setDaemon(true);
thread.setName("Fakereplace Thread");
thread.start();
} else {
System.out.println("Fakereplace is running.");
}
}
public static void redefine(ClassDefinition[] classes, AddedClass[] addedData) throws UnmodifiableClassException, ClassNotFoundException {
redefine(classes, addedData, true);
}
public static void redefine(ClassDefinition[] classes, AddedClass[] addedData, boolean wait) throws UnmodifiableClassException, ClassNotFoundException {
try {
for (AddedClass i : addedData) {
ClassFile cf = new ClassFile(new DataInputStream(new ByteArrayInputStream(i.getData())));
mainTransformer.addNewClass(new NewClassData(i.getClassName(), i.getLoader(), cf));
}
for (ClassDefinition i : classes) {
ClassDataStore.instance().markClassReplaced(i.getDefinitionClass());
BaseClassData baseClassData = ClassDataStore.instance().getBaseClassData(i.getDefinitionClass().getClassLoader(), i.getDefinitionClass().getName());
if (baseClassData != null) {
ClassDataStore.instance().saveClassData(i.getDefinitionClass().getClassLoader(), i.getDefinitionClass().getName(), new ClassDataBuilder(baseClassData));
}
}
// re-write the classes so their field
for (AddedClass c : addedData) {
ClassLookupManager.addClassInfo(c.getClassName(), c.getLoader(), c.getData());
}
inst.redefineClasses(classes);
Introspector.flushCaches();
if(wait) {
mainTransformer.waitForTasks();
}
} catch (Throwable e) {
try {
// dump the classes to /tmp so we can look at them
for (ClassDefinition d : classes) {
try {
ByteArrayInputStream bin = new ByteArrayInputStream(d.getDefinitionClassFile());
DataInputStream dis = new DataInputStream(bin);
final ClassFile file = new ClassFile(dis);
Transformer.getManipulator().transformClass(file, d.getDefinitionClass().getClassLoader(), true, new HashSet<>());
String dumpDir = AgentOptions.getOption(AgentOption.DUMP_DIR);
if (dumpDir != null) {
FileOutputStream s = new FileOutputStream(dumpDir + '/' + d.getDefinitionClass().getName() + "1.class");
DataOutputStream dos = new DataOutputStream(s);
file.write(dos);
dos.flush();
dos.close();
// s.write(d.getDefinitionClassFile());
s.close();
}
} catch (IOException a) {
a.printStackTrace();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
throw (new RuntimeException(e));
}
}
public static Instrumentation getInstrumentation() {
return inst;
}
public static Set<Extension> getIntegrationInfo(ClassLoader clr) {
final ServiceLoader<Extension> loader = ServiceLoader.load(Extension.class, clr);
final Set<Extension> integrations = new HashSet<Extension>();
final Iterator<Extension> it = loader.iterator();
while (it.hasNext()) {
integrations.add(it.next());
}
return integrations;
}
public static boolean isRetransformationStarted() {
return mainTransformer.isRetransformationStarted();
}
}